<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2014 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace Think\Model;

use Think\Model;

/**
 * ThinkPHP 聚合模型扩展
 */
class MergeModel extends Model {
	protected $modelList = array (); // 包含的模型列表 第一个必须是主表模型
	protected $masterModel = ''; // 主模型
	protected $joinType = 'INNER'; // 聚合模型的查询JOIN类型
	protected $fk = ''; // 外键名 默认为主表名_id
	protected $mapFields = array (); // 需要处理的模型映射字段，避免混淆 array( id => 'user.id' )
	
	/**
	 * 架构函数
	 * 取得DB类的实例对象 字段检查
	 * 
	 * @access public
	 * @param string $name
	 *        	模型名称
	 * @param string $tablePrefix
	 *        	表前缀
	 * @param mixed $connection
	 *        	数据库连接信息
	 */
	public function __construct($name = '', $tablePrefix = '', $connection = '') {
		parent::__construct ( $name, $tablePrefix, $connection );
		// 聚合模型的字段信息
		if (empty ( $this->fields ) && ! empty ( $this->modelList )) {
			$fields = array ();
			foreach ( $this->modelList as $model ) {
				// 获取模型的字段信息
				$result = $this->db->getFields ( M ( $model )->getTableName () );
				$_fields = array_keys ( $result );
				// $this->mapFields = array_intersect($fields,$_fields);
				$fields = array_merge ( $fields, $_fields );
			}
			$this->fields = $fields;
		}
		
		// 设置第一个模型为主表模型
		if (empty ( $this->masterModel ) && ! empty ( $this->modelList )) {
			$this->masterModel = $this->modelList [0];
		}
		// 主表的主键名
		$this->pk = M ( $this->masterModel )->getPk ();
		
		// 设置默认外键名 仅支持单一外键
		if (empty ( $this->fk )) {
			$this->fk = strtolower ( $this->masterModel ) . '_id';
		}
	}
	
	/**
	 * 得到完整的数据表名
	 * 
	 * @access public
	 * @return string
	 */
	public function getTableName() {
		if (empty ( $this->trueTableName )) {
			$tableName = array ();
			$models = $this->modelList;
			foreach ( $models as $model ) {
				$tableName [] = M ( $model )->getTableName () . ' ' . $model;
			}
			$this->trueTableName = implode ( ',', $tableName );
		}
		return $this->trueTableName;
	}
	
	/**
	 * 自动检测数据表信息
	 * 
	 * @access protected
	 * @return void
	 */
	protected function _checkTableInfo() {
	}
	
	/**
	 * 新增聚合数据
	 * 
	 * @access public
	 * @param mixed $data
	 *        	数据
	 * @param array $options
	 *        	表达式
	 * @param boolean $replace
	 *        	是否replace
	 * @return mixed
	 */
	public function add($data = '', $options = array(), $replace = false) {
		if (empty ( $data )) {
			// 没有传递数据，获取当前数据对象的值
			if (! empty ( $this->data )) {
				$data = $this->data;
				// 重置数据
				$this->data = array ();
			} else {
				$this->error = L ( '_DATA_TYPE_INVALID_' );
				return false;
			}
		}
		// 启动事务
		$this->startTrans ();
		// 写入主表数据
		$result = M ( $this->masterModel )->strict ( false )->add ( $data );
		if ($result) {
			// 写入外键数据
			$data [$this->fk] = $result;
			$models = $this->modelList;
			array_shift ( $models );
			// 写入附表数据
			foreach ( $models as $model ) {
				$res = M ( $model )->strict ( false )->add ( $data );
				if (! $res) {
					$this->rollback ();
					return false;
				}
			}
			// 提交事务
			$this->commit ();
		} else {
			$this->rollback ();
			return false;
		}
		return $result;
	}
	
	/**
	 * 对保存到数据库的数据进行处理
	 * 
	 * @access protected
	 * @param mixed $data
	 *        	要操作的数据
	 * @return boolean
	 */
	protected function _facade($data) {
		
		// 检查数据字段合法性
		if (! empty ( $this->fields )) {
			if (! empty ( $this->options ['field'] )) {
				$fields = $this->options ['field'];
				unset ( $this->options ['field'] );
				if (is_string ( $fields )) {
					$fields = explode ( ',', $fields );
				}
			} else {
				$fields = $this->fields;
			}
			foreach ( $data as $key => $val ) {
				if (! in_array ( $key, $fields, true )) {
					unset ( $data [$key] );
				} elseif (array_key_exists ( $key, $this->mapFields )) {
					// 需要处理映射字段
					$data [$this->mapFields [$key]] = $val;
					unset ( $data [$key] );
				}
			}
		}
		
		// 安全过滤
		if (! empty ( $this->options ['filter'] )) {
			$data = array_map ( $this->options ['filter'], $data );
			unset ( $this->options ['filter'] );
		}
		$this->_before_write ( $data );
		return $data;
	}
	
	/**
	 * 保存聚合模型数据
	 * 
	 * @access public
	 * @param mixed $data
	 *        	数据
	 * @param array $options
	 *        	表达式
	 * @return boolean
	 */
	public function save($data = '', $options = array()) {
		// 根据主表的主键更新
		if (empty ( $data )) {
			// 没有传递数据，获取当前数据对象的值
			if (! empty ( $this->data )) {
				$data = $this->data;
				// 重置数据
				$this->data = array ();
			} else {
				$this->error = L ( '_DATA_TYPE_INVALID_' );
				return false;
			}
		}
		if (empty ( $data )) {
			// 没有数据则不执行
			$this->error = L ( '_DATA_TYPE_INVALID_' );
			return false;
		}
		// 如果存在主键数据 则自动作为更新条件
		$pk = $this->pk;
		if (isset ( $data [$pk] )) {
			$where [$pk] = $data [$pk];
			$options ['where'] = $where;
			unset ( $data [$pk] );
		}
		$options ['join'] = '';
		$options = $this->_parseOptions ( $options );
		// 更新操作不使用JOIN
		$options ['table'] = $this->getTableName ();
		
		if (is_array ( $options ['where'] ) && isset ( $options ['where'] [$pk] )) {
			$pkValue = $options ['where'] [$pk];
		}
		if (false === $this->_before_update ( $data, $options )) {
			return false;
		}
		$result = $this->db->update ( $data, $options );
		if (false !== $result) {
			if (isset ( $pkValue ))
				$data [$pk] = $pkValue;
			$this->_after_update ( $data, $options );
		}
		return $result;
	}
	
	/**
	 * 删除聚合模型数据
	 * 
	 * @access public
	 * @param mixed $options
	 *        	表达式
	 * @return mixed
	 */
	public function delete($options = array()) {
		$pk = $this->pk;
		if (empty ( $options ) && empty ( $this->options ['where'] )) {
			// 如果删除条件为空 则删除当前数据对象所对应的记录
			if (! empty ( $this->data ) && isset ( $this->data [$pk] ))
				return $this->delete ( $this->data [$pk] );
			else
				return false;
		}
		
		if (is_numeric ( $options ) || is_string ( $options )) {
			// 根据主键删除记录
			if (strpos ( $options, ',' )) {
				$where [$pk] = array (
						'IN',
						$options 
				);
			} else {
				$where [$pk] = $options;
			}
			$options = array ();
			$options ['where'] = $where;
		}
		// 分析表达式
		$options ['join'] = '';
		$options = $this->_parseOptions ( $options );
		if (empty ( $options ['where'] )) {
			// 如果条件为空 不进行删除操作 除非设置 1=1
			return false;
		}
		if (is_array ( $options ['where'] ) && isset ( $options ['where'] [$pk] )) {
			$pkValue = $options ['where'] [$pk];
		}
		
		$options ['table'] = implode ( ',', $this->modelList );
		$options ['using'] = $this->getTableName ();
		if (false === $this->_before_delete ( $options )) {
			return false;
		}
		$result = $this->db->delete ( $options );
		if (false !== $result) {
			$data = array ();
			if (isset ( $pkValue ))
				$data [$pk] = $pkValue;
			$this->_after_delete ( $data, $options );
		}
		// 返回删除记录个数
		return $result;
	}
	
	/**
	 * 表达式过滤方法
	 * 
	 * @access protected
	 * @param string $options
	 *        	表达式
	 * @return void
	 */
	protected function _options_filter(&$options) {
		if (! isset ( $options ['join'] )) {
			$models = $this->modelList;
			array_shift ( $models );
			foreach ( $models as $model ) {
				$options ['join'] [] = $this->joinType . ' JOIN ' . M ( $model )->getTableName () . ' ' . $model . ' ON ' . $this->masterModel . '.' . $this->pk . ' = ' . $model . '.' . $this->fk;
			}
		}
		$options ['table'] = M ( $this->masterModel )->getTableName () . ' ' . $this->masterModel;
		$options ['field'] = $this->checkFields ( isset ( $options ['field'] ) ? $options ['field'] : '' );
		if (isset ( $options ['group'] ))
			$options ['group'] = $this->checkGroup ( $options ['group'] );
		if (isset ( $options ['where'] ))
			$options ['where'] = $this->checkCondition ( $options ['where'] );
		if (isset ( $options ['order'] ))
			$options ['order'] = $this->checkOrder ( $options ['order'] );
	}
	
	/**
	 * 检查条件中的聚合字段
	 * 
	 * @access protected
	 * @param mixed $data
	 *        	条件表达式
	 * @return array
	 */
	protected function checkCondition($where) {
		if (is_array ( $where )) {
			$view = array ();
			foreach ( $where as $name => $value ) {
				if (array_key_exists ( $name, $this->mapFields )) {
					// 需要处理映射字段
					$view [$this->mapFields [$name]] = $value;
					unset ( $where [$name] );
				}
			}
			$where = array_merge ( $where, $view );
		}
		return $where;
	}
	
	/**
	 * 检查Order表达式中的聚合字段
	 * 
	 * @access protected
	 * @param string $order
	 *        	字段
	 * @return string
	 */
	protected function checkOrder($order = '') {
		if (is_string ( $order ) && ! empty ( $order )) {
			$orders = explode ( ',', $order );
			$_order = array ();
			foreach ( $orders as $order ) {
				$array = explode ( ' ', trim ( $order ) );
				$field = $array [0];
				$sort = isset ( $array [1] ) ? $array [1] : 'ASC';
				if (array_key_exists ( $field, $this->mapFields )) {
					// 需要处理映射字段
					$field = $this->mapFields [$field];
				}
				$_order [] = $field . ' ' . $sort;
			}
			$order = implode ( ',', $_order );
		}
		return $order;
	}
	
	/**
	 * 检查Group表达式中的聚合字段
	 * 
	 * @access protected
	 * @param string $group
	 *        	字段
	 * @return string
	 */
	protected function checkGroup($group = '') {
		if (! empty ( $group )) {
			$groups = explode ( ',', $group );
			$_group = array ();
			foreach ( $groups as $field ) {
				// 解析成聚合字段
				if (array_key_exists ( $field, $this->mapFields )) {
					// 需要处理映射字段
					$field = $this->mapFields [$field];
				}
				$_group [] = $field;
			}
			$group = implode ( ',', $_group );
		}
		return $group;
	}
	
	/**
	 * 检查fields表达式中的聚合字段
	 * 
	 * @access protected
	 * @param string $fields
	 *        	字段
	 * @return string
	 */
	protected function checkFields($fields = '') {
		if (empty ( $fields ) || '*' == $fields) {
			// 获取全部聚合字段
			$fields = $this->fields;
		}
		if (! is_array ( $fields ))
			$fields = explode ( ',', $fields );
			
			// 解析成聚合字段
		$array = array ();
		foreach ( $fields as $field ) {
			if (array_key_exists ( $field, $this->mapFields )) {
				// 需要处理映射字段
				$array [] = $this->mapFields [$field] . ' AS ' . $field;
			} else {
				$array [] = $field;
			}
		}
		$fields = implode ( ',', $array );
		return $fields;
	}
	
	/**
	 * 获取数据表字段信息
	 * 
	 * @access public
	 * @return array
	 */
	public function getDbFields() {
		return $this->fields;
	}
}